Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.

...powered by www.netzwerkartist.de...

 << zurück
Visual C# 2005 von Andreas Kühnel
Das umfassende Handbuch
Buch: Visual C# 2005

Visual C# 2005
1.320 S., mit 2 CDs, 59,90 Euro
Galileo Computing
ISBN 3-89842-586-X
gp Kapitel 6 Vererbung, Polymorphie und Schnittstellen
  gp 6.1 Basisklassen und abgeleitete Klassen
    gp 6.1.1 Ableiten einer Klasse
    gp 6.1.2 Klassen, die nicht vererben können
    gp 6.1.3 Zusammenfassung
  gp 6.2 Konstruktoren in abgeleiteten Klassen
    gp 6.2.1 Die Konstruktoren der Klasse »GraphicCircle«
    gp 6.2.2 Der Zugriffsmodifizierer »protected«
    gp 6.2.3 Konstruktorverkettung
    gp 6.2.4 Destruktor-Verkettung
    gp 6.2.5 Der Stand des Projekts »CircleApplication«
    gp 6.2.6 Zusammenfassung
  gp 6.3 Die Methoden in einer abgeleiteten Klasse
    gp 6.3.1 Geerbte Methoden mit »new« verdecken
    gp 6.3.2 Überladen einer Basisklassenmethode
  gp 6.4 Ereignisse in der Vererbung
  gp 6.5 »Hat-eine«-Beziehungen (Aggregation)
    gp 6.5.1 Weiterleitung einer internen Objektreferenz
    gp 6.5.2 Verbergen des internen Objekts
    gp 6.5.3 Innere Klassen
  gp 6.6 Typumwandlung von Objektvariablen
    gp 6.6.1 Die implizite Typumwandlung von Objektreferenzen
    gp 6.6.2 Die explizite Typumwandlung von Objektreferenzen
    gp 6.6.3 Zusammenfassung
  gp 6.7 Abstrakte Klassen und Methoden
    gp 6.7.1 Abstrakte Definitionen
  gp 6.8 Polymorphie
    gp 6.8.1 Virtuelle Methoden
    gp 6.8.2 Inhomogene Mengen
    gp 6.8.3 Verdecken und Überschreiben geerbter Methoden
    gp 6.8.4 Überschreiben der Methode »ToString()« der Klasse »Object«
    gp 6.8.5 Versiegelte Methoden
    gp 6.8.6 Zusammenfassung
  gp 6.9 Erweiterung der Klassenhierarchie »CircleApplication«
    gp 6.9.1 Die Klasse »GeometricObject«
  gp 6.10 Schnittstellen
    gp 6.10.1 Einführung in die Schnittstellen
    gp 6.10.2 Schnittstellendefinition
    gp 6.10.3 Schnittstellenimplementierung
    gp 6.10.4 Typumwandlung mit dem »as«-Operator
    gp 6.10.5 Abstrakte Klassen vs. Schnittstellen
    gp 6.10.6 Zusammenfassung


Galileo Computing

6.6 Typumwandlung von Objektvariabledowntop

Kommen wir noch einmal auf das Beispiel der Klassenhierarchie der Luftfahrzeuge zurück. Die Klasse Luftfahrzeug beschreibt Eigenschaften und Operationen, die allen Luftfahrzeugen, unabhängig vom Typ, eigen sind.


Galileo Computing

6.6.1 Die implizite Typumwandlung von Objektreferenzen  downtop

Die Klassen Zeppelin, Starrflügler und Hubschrauber beerben als abgeleitete Klassen die Basisklasse. Am Beispiel des Zeppelins lautet der Code:


class Luftfahrzeug {
  // Anweisungen
}
class Zeppelin : Luftfahrzeug {
  // Anweisungen
}

Zwischen diesen beiden Klassen liegt eine Ist-eine-Beziehung vor: Ein Objekt vom Typ Zeppelin ist gleichzeitig auch ein Objekt vom Typ Luftfahrzeug. Diese Zuordnung hat Konsequenzen, denn aufgrund dieser Beziehung kann man die Referenz auf ein Subklassenobjekt einer Referenz auf ein Basisklassenobjekt zuweisen:

Zeppelin zpln = new Zeppelin();
Luftfahrzeug lfzg = zpln;
3
Stehen zwei Klassen miteinander in einer Vererbungsbeziehung, kann eine Referenz vom Typ der abgeleiteten Klasse der Referenz vom Typ einer der Basisklassen mit <Basisklassenreferenz> = <Subklassenreferenz> zugewiesen werden. Dabei erfolgt eine implizite Konvertierung.

Die beiden Variablen zpln und lfzg referenzieren denselben Speicherbereich – jedoch mit einer kleinen Einschränkung: Die Laufzeitumgebung betrachtet lfzg nur als Objekt vom Typ Luftfahrzeug und nicht als Zeppelin. Damit hat die Objektreferenz lfzg auch keinen Zugriff auf die Operationen, durch die sich ein Objekt vom Typ Zeppelin auszeichnet.

Die Tatsache, dass ein Objekt vom Typ einer abgeleiteten Klasse auch gleichzeitig ein Objekt vom Typ seiner Basisklasse ist, kann man sich bei der Typfestlegung eines Parameters zunutze machen:


public void TestProc(Luftfahrzeug obj) {
  // Anweisungen
}

Die Methode TestProc erwartet vom Aufrufer die Referenz auf ein Luftfahrzeug. Ob es sich dabei um einen Zeppelin, einen Hubschrauber oder einen sonstigen Typ handelt, spielt keine Rolle. Ausschlaggebend ist nur, dass der Typ der übergebenen Referenz von Luftfahrzeug abgeleitet ist. Zeppelin erfüllt diese Bedingung. Daher kann die Methode TestProc folgendermaßen aufgerufen werden:


Zeppelin zpln = new Zeppelin();
IrgendEinObjekt.TestProc(zpln);

Der umgekehrte Fall, nämlich die Zuweisung einer Basisklassenreferenz an eine Referenz vom Typ der abgeleiteten Klasse, führt zu einer Fehlermeldung:


Luftfahrzeug lfzg = new Luftfahrzeug(); 
// die folgende Anweisung ist falsch
Zeppelin zpln = lfzg;

Der Grund des Fehlers ist einfach zu verstehen: Das Objekt einer abgeleiteten Klasse kann mehr zustandsbeschreibende Daten (also Felder) aufweisen als das Objekt seiner Basisklasse. Die Methoden der Subklasse müssen aber auf die typspezifischen Daten zugreifen können. Genau hier liegt der springende Punkt, wenn der Versuch unternommen wird, mit


<Subklassenreferenz> = <Basisklassenreferenz>  // Kompilierfehler

den Compiler hinters Licht zu führen.

Das Verhalten lässt sich auch anschaulich erklären. Alle Felder eines bestimmten Typs werden über eine Schnittstelle offen gelegt – ob es sich dabei um eine öffentliche Deklaration eines Feldes oder um das Veröffentlichen eines privaten Feldes über eine Eigenschaftsmethode handelt, spielt keine Rolle. Bei einer Zuweisung nach dem Muster


<Basisklassenreferenz> = <Subklassenreferenz>

müssen alle Entitäten der links vom Zuweisungsoperator angegebenen Referenz einen konkreten Bezug zu einem Mitglied der rechts vom Zuweisungsoperator stehenden Referenz haben. Betrachten Sie dazu die folgende Abbildung, die diesen Sachverhalt anschaulich darstellt. Dass dabei das Feld Gasvolumen einer Zeppelin-Referenz keinen Abnehmer in der Luftfahrzeug-Referenz findet, spielt keine Rolle.

Würde die Referenz vom Typ der Basisklasse einer Referenz vom Typ der abgeleiteten Klasse zugewiesen, hätte das beim Zugriff auf das objektspezifische Feld Gasvolumen des Zeppelins fatale Folgen, denn das Feld wäre nicht initialisiert. Der C#-Compiler versagt deshalb die Kompilierung.

Abbildung
Hier klicken, um das Bild zu vergrößern

Abbildung 6.4   Die Zuweisung einer Subklassenreferenz an eine Basisklassenreferenz


Galileo Computing

6.6.2 Die explizite Typumwandlung von Objektreferenzedowntop

In Kapitel 3 haben Sie bereits gelernt, einen elementaren Datentyp in einen anderen umzuwandeln. Diese Umwandlung wird als Konvertierung oder Casting bezeichnet. Eine Konvertierung wird implizit – also ohne Interaktion seitens des Entwicklers – ausgeführt, wenn die Operation eine Aufweitung des Datentyps zur Folge hat. Das ist beispielsweise der Fall, wenn der Inhalt eines int einem long zugewiesen wird:


int intVar = 4711;
long lngVar = intVar;

Die umgekehrte Zuweisung, also eine einengende Operation, muss ausdrücklich erzwungen werden. Dazu dient der Konvertierungsoperator:


long lngVar = 4711;
int intVar = (int)lngVar;

Von einer operativen Aufweitung spricht man auch, wenn man eine Subklassenreferenz einer Basisklassenreferenz zuweist, also in der Vererbungshierarchie dem Beziehungspfeil nach oben folgt. So wie ein long allgemeiner ist als ein int, weil er den gesamten Wertebereich des int abdeckt, gilt auch eine Basisklasse allgemeiner als eine Subklasse, denn die Subklasse enthält ohne jeden Zweifel alle Mitglieder der Basisklasse.

Nehmen wir zum Beispiel unsere beiden Klassen Circle und GraphicCircle. Dabei sei myCircle die Referenz auf ein Circle-Objekt und myGraphCircle die Referenz auf ein GraphicCircle-Objekt.


Circle myCircle = new Circle();
GraphicCircle myGraphCircle;

Die Zuweisung


myCircle = myGraphCircle;

konvertiert implizit, weil sich die Typen beider Referenzen in einer Vererbungsbeziehung befinden und das Versprechen der Referenz myGraphCircle, allen Mitgliedern von myCircle etwas übergeben zu können, erfüllt werden kann.

Um die umgekehrte Zuweisung zu erzwingen, kommt der Konvertierungsoperator zum Einsatz:


myGraphCircle = (GraphicCircle)myCircle ;

Selbstverständlich meldet sich der C#-Compiler mit einem Fehlerhinweis, wenn keine Möglichkeit besteht, eine erfolgreiche Typumwandlung vorzunehmen, beispielsweise:


// fehlerhafte Typumwandlung
ClassA obj = (ClassA)myCircle;

Der Typ der Referenz myCircle befindet sich in keiner Vererbungsbeziehung zu ClassA, daher ist der Versuch der Typumwandlung zum Scheitern verurteilt. Eine Vererbungsbeziehung ist also eine unausweichliche Voraussetzung der expliziten Konvertierung.

Eine explizite Typumwandlung wird dann durchgeführt, wenn man nach einer impliziten Konvertierung ein Objekt wieder mit allen seinen Fähigkeiten benutzen möchte. Meist ist das der Fall, nachdem es zunächst in einen allgemeineren Typ umgewandelt worden ist. Versuchen wir, uns das anhand einer kleinen, überschaubaren Vererbungshierarchie zu verdeutlichen.


class BaseClass {
  public void BaseClassMethod() {
    Console.WriteLine("In BaseClass");
  }
}
class SubClassA : BaseClass {
  public void SubClassAMethod() {
    Console.WriteLine("In SubClassA");
  }
}
class SubClassB : BaseClass {
  public void SubClassBMethod() {
    Console.WriteLine("In SubClassB");
  }
}

Die Klasse BaseClass bildet die Basisklasse der beiden abgeleiteten Klassen SubClassA und SubClassB. In allen Klassen ist jeweils eine typspezifische Methode definiert, die eine Zeichenfolge im Befehlsfenster anzeigt.

Bisher ist noch nichts Neues zu erkennen. Interessant wird es aber, wenn wir Objekte der verschiedenen Typen erzeugen. Wir machen das aber nicht, indem wir Objekt für Objekt des entsprechenden Typs erstellen, wir wollen dazu ein Objekt-Array benutzen:


BaseClass[] objArr = new BaseClass[10];

Das Array ist vom Typ BaseClass. Weil die beiden Klassen SubClassA und SubClassB von diesem Typ abgeleitet sind, kann jedes Element des Arrays auch die Referenz auf ein Objekt vom Typ der beiden Subklassen aufnehmen:


objArr[0] = new SubClassA();
objArr[1] = new SubClassB();
objArr[2] = new BaseClass();
...
objArr[9] = new BaseClass();

Wollen wir nun beispielsweise die Methode SubClassAMethod auf alle Objekte vom Typ SubClassA aufrufen, muss jedes Array-Element zuerst einer Typüberprüfung unterzogen werden, um festzustellen, ob sich hinter der Referenz auch der gesuchte Typ verbirgt. Dazu wird das Array in einer Schleife durchlaufen und jedes Element mit dem Operator is typgeprüft:


for(int i = 0; i < objArr.Length; i++) {
  if(objArr[i] is SubClassA)
    ...
}

Ist das überprüfte Array-Element vom Typ SubClassA, soll dessen spezifische Methode SubClassAMethod aufgerufen werden. Mit


objArr[0].SubClassAMethod();

ziehen wir uns allerdings den Zorn des Compilers zu – es kommt zu einem Kompilierfehler. Wegen der Regel zur impliziten Konvertierung

<Basisklassenreferenz> = <Subklassenreferenz>

haben wir nur Zugriff auf die Klassenmitglieder, die beiden Typen gemein sind, denn das Array ist vom Typ BaseClass deklariert und nicht vom Typ einer der abgeleiteten Klassen. Deshalb stehen auch nur die Mitglieder zur Verfügung, die in der Basisklasse definiert sind und den Subklassen vererbt werden. Die objektspezifische Methode, die wir eigentlich aufrufen wollen, gehört nicht dazu.

Um das Problem zu lösen, muss das gefundene Array-Element zuerst explizit in den Typ SubClassA umgewandelt werden. Danach können wir auch die typspezifische Methode ausführen:


((SubClassA)objArr[i]).SubClassAMethod();

Beachten Sie hierbei, dass der gesamte Ausdruck links vom Punktoperator in Klammern gesetzt werden muss, um auf das konvertierte objArr[i]-Element die gewünschte Methode aufzurufen – der Punktoperator hat eine höhere Priorität als der Konvertierungsoperator.

Die gesamte Anweisungsblock sieht vollständig folgendermaßen aus:


for(int i = 0; i < objArr.Length; i++) {
  if(objArr[i] is SubClassA)
    ((SubClassA)objArr[i]).SubClassAMethod();
}

Es kann manchmal sinnvoll sein, eine größere Anzahl verschiedener Objekte, deren Typ sich in einer Vererbungsbeziehung befindet, in einem Array zusammenzufassen. Bedingung ist dabei nur, dass der Typ des Arrays Basisklasse jedes vom Array verwalteten Objekttyps ist. Mit der expliziten Konvertierung kann zu jedem Zeitpunkt wieder in den tatsächlichen Typ gecastet werden, ohne dabei wertvolle Informationen zu verlieren. Im Extremfall ließen sich sogar alle Typen der .NET-Klassenbibliothek, einschließlich der benutzerdefinierten, in einem Array zusammenfassen. Dieser müsste dann vom Typ Object sein, da sich ausnahmslos jeder Typ des .NET Frameworks auf diese Wurzel zurückführen lässt. Ob es sinnvoll ist, aus »Sicherheitsgründen« Object als Typ zu wählen, ist allerdings zweifelhaft, denn je allgemeiner der Typ gehalten wird, desto fehleranfälliger wird der Code.

Explizite Konvertierungen folgen immer der Richtung »von oben nach unten« und sind nur möglich, wenn zwei Bedingungen erfüllt sind:

1.  Der Typ, in den gecastet wird, steht in einer Vererbungslinie zum Ausgangstyp.
2. Der Verweis der zu konvertierenden Ausgangsreferenz wurde zuvor implizit gecastet.
       

Ist SubClass von BaseClass abgeleitet, wird im folgenden Codefragment daher ein Kompilierfehler auftreten, weil der zweite Punkt nicht erfüllt ist:


// unzulässige Konvertierung
BaseClass baseObj = new BaseClass();
SubClass subObj = (SubClass)baseObj;

Die explizite Konvertierung innerhalb einer Vererbungshierarchie auf horizontaler Ebene in einer verzweigten Klassenhierarchie, beispielsweise vom Typ SubClassA in den Typ SubClassC (siehe Abbildung 6.5), ist nicht gestattet, da die Aussage, dass ein Objekt vom Typ SubClassA gleichzeitig auch ein Objekt vom Typ SubClassC sei, falsch ist.

Abbildung
Hier klicken, um das Bild zu vergrößern

Abbildung 6.5   Verzweigte Klassenhierarchie


Galileo Computing

6.6.3 Zusammenfassung  toptop

gp  Die von einer Basisklasse geerbte Methode kann in der abgeleiteten Klasse neu implementiert werden. Die Neuimplementierung verdeckt dann die geerbte Methode. Voraussetzung ist eine identische Signatur gemäß den Regeln der Methodenüberladung. Die Methode, die eine geerbte Methode verdeckt, muss zusätzlich mit dem Modifizierer new signiert werden.
gp  Das komplette Ausblenden eines geerbten Mitglieds in einer abgeleiteten Klasse ist nicht möglich.
gp  Um einen abgeleiteten Typ zu spezialisieren, kann die von einer Basisklasse geerbte Methode in der abgeleiteten Klasse überladen werden. Die überladene Methode darf sich nur in der Parameterliste von der geerbten, die weiterhin in der Subklasse sichtbar ist, unterscheiden.
gp  Eine Ist-eine-Beziehung beschreibt die Beziehung zweier Klassen in einer Vererbungslinie. Eine Hat-eine-Beziehung (Aggregation) liegt dann vor, wenn innerhalb einer Klasse ein Feld ein Objekt referenziert. Der Benutzer hat auf das Objekt entweder direkten Zugriff über eine Eigenschaft oder über die Aufrufumleitung einer zwischengeschalteten typspezifischen Methode.
gp  Ist ein Objekt an die Existenz eines anderen gebunden, bietet es sich an, das abhängige Objekt in der umgebenden Klasse zu definieren. Man spricht dann auch von einer inneren Klasse.
gp  Wird einer Variablen vom Typ einer Basisklasse die Referenz auf ein Objekt einer Subklasse zugewiesen, erfolgt eine implizite Typkonvertierung. Die Zuweisung einer Basisklassenreferenz an eine Variable vom Typ der Subklasse ist nur dann möglich, wenn die zuzuweisende Referenz mit dem ()-Operator konvertiert wird und sich beide Klassen auf einer Vererbungslinie befinden.
 << zurück
  
  Zum Katalog
Zum Katalog: Visual C# 2005
Visual C# 2005
bestellen
 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchtipps
Zum Katalog: Fortgeschrittene Programmierung mit Visual C# 2005






 Fortgeschrittene
 Programmierung
 mit Visual C# 2005


Zum Katalog: Einstieg in Visual C# 2005






 Einstieg in
 Visual C# 2005


Zum Katalog: Einstieg in Visual Basic 2005






 Einstieg in
 Visual Basic 2005


Zum Katalog: Visual Basic 2005






 Visual Basic 2005


Zum Katalog: Java ist auch eine Insel






 Java ist auch eine
 Insel


Zum Katalog: Konzepte und Lösungen für Microsoft-Netzwerke






 Konzepte und
 Lösungen für
 Microsoft-Netzwerke


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo








Copyright © Galileo Press 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de